package cn.bearspark.tag.provider.service.impl;

import cn.bearspark.common.assess.TimedViewedDataAssessor;
import cn.bearspark.common.dto.Page;
import cn.bearspark.common.lock.Lock;
import cn.bearspark.common.lock.SpinLock;
import cn.bearspark.common.util.ConvertBeanUtil;
import cn.bearspark.common.util.ConvertInfoUtil;
import cn.bearspark.tag.dto.TagDTO;
import cn.bearspark.tag.provider.dao.buffer.RecentTop9TagIdBuffer;
import cn.bearspark.tag.provider.dao.buffer.TagHashBuffer;
import cn.bearspark.tag.provider.dao.co.TagCO;
import cn.bearspark.tag.provider.dao.repository.TagContentRepository;
import cn.bearspark.tag.provider.dao.mapper.TagBaseMapper;
import cn.bearspark.tag.provider.dao.po.TagIdContentPO;
import cn.bearspark.tag.provider.service.TagService;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author f
 */
@Service
@EnableScheduling
public class TagServiceImpl implements TagService {

    /**
     * 刷新标签浏览量时使用的锁
     */
    private final Lock refreshViewNumLock = new SpinLock();

    /**
     * 刷新标签讨论量时使用的锁
     */
    private final Lock refreshDiscussNumLock = new SpinLock();

    /**
     * 积累标签浏览量增量的映射，key：标签的 id，value：标签浏览量的增量
     */
    private final Map<Long, Long> tagViewNumMap = new ConcurrentHashMap<>();

    /**
     * 积累标签讨论量增量的映射，key：标签的 id，value：标签讨论量的增量
     */
    private final Map<Long, Long> tagDiscussNumMap = new ConcurrentHashMap<>();

    /**
     * 最热标签的评估器，时间因子为 0.7
     */
    private final TimedViewedDataAssessor topAssessor = new TimedViewedDataAssessor(0.7f);

    /**
     * 最近标签的评估器，时间因子为 0.95
     */
    private final TimedViewedDataAssessor recentAssessor = new TimedViewedDataAssessor(0.95f);

    private final TagBaseMapper tagBaseMapper;

    private final TagHashBuffer tagHashBuffer;
    private final RecentTop9TagIdBuffer recentTop9TagIdBuffer;

    private final TagContentRepository tagContentRepository;

    public TagServiceImpl(TagBaseMapper tagBaseMapper,
                          TagHashBuffer tagHashBuffer,
                          RecentTop9TagIdBuffer recentTop9TagIdBuffer,
                          TagContentRepository tagContentRepository) {
        this.tagBaseMapper = tagBaseMapper;
        this.tagHashBuffer = tagHashBuffer;
        this.recentTop9TagIdBuffer = recentTop9TagIdBuffer;
        this.tagContentRepository = tagContentRepository;
    }

    @Override
    public Page<TagDTO> pageQuery(String keyword, int pageNum, int pageSize) {
        Page<TagIdContentPO> tagIdContentPOPage = tagContentRepository.pageQuery(keyword, pageNum, pageSize);
        Page<TagDTO> tagDTOPage = new Page<>();
        tagDTOPage.setTotal(tagIdContentPOPage.getTotal());
        tagDTOPage.setRecords(tagHashBuffer.page(tagIdContentPOPage.getRecords()).stream()
                .map(tagBasePO -> ConvertInfoUtil.convert(tagBasePO, TagDTO.class))
                .toList());
        return tagDTOPage;
    }

    @Override
    public List<TagDTO> getTop9RecentTag() {
        return ConvertInfoUtil.convertList(tagHashBuffer.list(recentTop9TagIdBuffer.listRT9TagId()), TagDTO.class);
    }

    @Override
    public Map<Long, String> getContentMap(List<Long> tagIdList) {
        return tagHashBuffer.mapContent(tagIdList);
    }

    @Override
    public String getContent(long tagId) {
        return tagHashBuffer.getContent(tagId);
    }

    @Override
    public void incrViewNum(long tagId) {
        refreshViewNumLock.lock();
        try {
            tagViewNumMap.compute(tagId, (k, v) -> v == null ? 1 : v + 1);
        } finally {
            refreshViewNumLock.unlock();
        }
    }

    @Override
    public void incrDiscussNum(long tagId) {
        refreshDiscussNumLock.lock();
        try {
            tagDiscussNumMap.compute(tagId, (k, v) -> v == null ? 1 : v + 1);
        } finally {
            refreshDiscussNumLock.unlock();
        }
    }

    /**
     * 每隔 5s，刷新数据库中的数据
     */
    @Scheduled(fixedRate = 5000)
    public void refresh() {
        // 如果映射中没有数据，则无需刷新
        int viewNumSize = tagViewNumMap.size();
        int discussNumSize = tagDiscussNumMap.size();
        if (viewNumSize == 0 && discussNumSize == 0) {
            return;
        }

        // 获取要刷新的标签 id，并将映射置为空映射
        int size = Math.max(viewNumSize, discussNumSize);
        Set<Long> refreshTagIdSet = new HashSet<>(size);
        Map<Long, Long> deltaViewNumMap = new HashMap<>(viewNumSize);
        Map<Long, Long> deltaDiscussNumMap = new HashMap<>(discussNumSize);

        // 统计需要刷新的标签中浏览量的增量
        refreshViewNumLock.lock();
        try {
            tagViewNumMap.forEach((tagId, deltaViewNum) -> {
                deltaViewNumMap.put(tagId, deltaViewNum);

                refreshTagIdSet.add(tagId);
            });

            tagViewNumMap.clear();
        } finally {
            refreshViewNumLock.unlock();
        }

        // 统计需要刷新的标签中讨论量的增量
        refreshDiscussNumLock.lock();
        try {
            tagDiscussNumMap.forEach((tagId, deltaDiscussNum) -> {
                deltaDiscussNumMap.put(tagId, deltaDiscussNum);

                refreshTagIdSet.add(tagId);
            });

            tagDiscussNumMap.clear();
        } finally {
            refreshDiscussNumLock.unlock();
        }

        // 在缓存中查询这些数据
        Map<Long, Double> topScoreMap = new HashMap<>();
        Map<Long, TagCO> tagCOMap = tagHashBuffer.map(refreshTagIdSet);
        tagCOMap.forEach((tagId, tagCO) -> {
            Long deltaViewNum = deltaViewNumMap.get(tagId);
            if (deltaViewNum != null) {
                tagCO.setViewNum(deltaViewNum + tagCO.getViewNum());

                // 当改变标签的浏览量时，需要重新计算分数
                double topScore = topAssessor.calScore(tagCO);
                tagCO.setTopScore(topScore);
                topScoreMap.put(tagId, topScore);
                tagCO.setRecentScore(recentAssessor.calScore(tagCO));
            }
            Long deltaDiscussNum = deltaDiscussNumMap.get(tagId);
            if (deltaDiscussNum != null) {
                tagCO.setDiscussNum(deltaDiscussNum + tagCO.getDiscussNum());
            }
        });

        // 更新缓存中的标签数据
        tagHashBuffer.cacheBatch(tagCOMap);

        // 更新 mysql 数据库中的标签数据
        tagHashBuffer.list(refreshTagIdSet).forEach(tagBaseMapper::update);

        // 如果标签的浏览量改变了，则需要更新 es 数据库中的标签数据
        if (viewNumSize == 0) {
            tagContentRepository.refresh(topScoreMap);
        }
    }
}
